﻿using System;
using System.Windows;
using System.Collections.Generic;
using System.Text;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Controls;
using System.Windows.Media.Animation;
using System.Windows.Data;


namespace mates_viz
{
    class UIArc
    {

        UILocation parentLoc;					// points to this arc's location
        Path arcPath;
		SolidColorBrush strokeBrush;

		private HighlightConverter _theConverter = null;
		public Bind myBindDef;
		public Boolean bHasValidBinding = false;

		MyData myChangedData = new MyData();

		public HighlightConverter TheConverter
		{
			get
			{
				if (_theConverter == null)
					_theConverter = new HighlightConverter();

				return _theConverter;
			}
		}


        //TransformDecorator TDArc;
        public Canvas arcCanvas;
        private bool removeWhenDone = false;    // if set true, the arc will ask its parent loc
                                                // to remove it when its animation is complete.
        RotateTransform Rotate;

		RotateTransform userInPlaceRotate;
        
        LinkedList<UIUser> Users = new LinkedList<UIUser>();
        public UIUser FirstUser { get { return (Users.Count == 0) ? null : Users.First.Value; } }
        public UIUser LastUser { get { return (Users.Count == 0) ? null : Users.Last.Value; } }

        // indicated this arc's start & end within its ring
        int startTick = 0; public int StartTick { get { return startTick; } }
        int endTick = 0; public int EndTick { get { return endTick; } }
        int ringIndex; public int RingIndex { get { return ringIndex; } }
        
        Ring arcRing;

		private bool firstInLoc
		{
			get
			{
				return false;
				UIArc firstInLoc = parentLoc.FirstArc;
				// if we're either explicitly the first arc, or our parent loc hasn't event
				// added this arc to the list yet (null) -- then this arc is the first arc.
				return ((firstInLoc == this) || (firstInLoc == null)) ? true : false;
			}
		}

        // shortcuts to ring functions
		double outerRadius { get { return arcRing.innerRadius + arcRing.thickness; } }
		// if this arc is the first one in the loc, it has a normal inner radius, else it's pulled closer to its predecessor
        double innerRadius { get { return firstInLoc ? arcRing.innerRadius : arcRing.innerRadius - Ring.arcGap; } }
		double thickness { get { return firstInLoc ? arcRing.thickness : arcRing.thickness + Ring.arcGap; } }

		
		double startAngle { get { return arcRing.angleOf(startTick); } }
        double endAngle { get { return arcRing.angleOf(endTick); } }

        public bool FillsRing   // check if there's room in the ring for more users.
        { get { return endTick == arcRing.maxTick; }}

        public bool HighlightUser(string username, bool on, string Highlight_Type)
        {  
            // Search the linked list for the user.
            LinkedListNode<UIUser> user = Users.First;
            while (user != null)
            {
                if (user.Value.Username == username)
                {
                    if (on)
                    {
                        TimelineClock anim_Clock =
                            Style.FindStoryboardClock(user.Value.userButton, Highlight_Type);
                        anim_Clock.ClockController.Begin();
                    }
                    else
                    {
                        TimelineClock anim_Clock =
                            Style.FindStoryboardClock(user.Value.userButton, Highlight_Type);
                        anim_Clock.ClockController.Deactivate();
                    }

                    // always fade in the new user
					//DoubleAnimation flashUserAnimation = new DoubleAnimation(
					//    0.6,    // from transparent
					//    1.0,    // to totally opaque
					//    new Duration(TimeSpan.FromSeconds(0.5)), FillBehavior.HoldEnd);
					//flashUserAnimation.BeginTime = TimeManager.CurrentGlobalTime;
					//flashUserAnimation.AutoReverse = true;
					//user.Value.userButton.GetAnimations(Button.OpacityProperty).Clear();
					//user.Value.userButton.GetAnimations(Button.OpacityProperty).Add(flashUserAnimation);

                    return true;
                }
                user = user.Next;
            }

            return false;  // if we haven't found the user by now, they're not here.
        }

		public void HighlightSelf(bool on)	
		{
			// modification is achieved through a changeablereference brush type
			// highlight or un-highlight a loc by modifying its edge color
			//arcPath.Stroke.VerifyAccess()

			myChangedData.Highlighted = on;	 // property triggers automatic color update

			arcPath.Stroke.SetValue(Path.StrokeProperty, Brushes.White);



		}
		
        public bool ContainsUser(string username)
        {
            // Search the linked list for the user.
            LinkedListNode<UIUser> user = Users.First;
            while( user != null)
            {   if (user.Value.Username == username) return true;
                user = user.Next;
            }

            return false;   // if we didn't find the user anywhere in the 
                            // list, return false.
        }

        //public void AddUser(string username)
        //{
        //    // calls adduser, but defaults to having the user appear from
        //    // the index BEFORE the last user.

        //    int fromTick = -1;
        //    if (Users.Count > 0) fromTick = Users.Last.Value.tickIndex;
        //    AddUser(username, fromTick);
        //}

        public void AddUserToFront(string username)
        {

			// before we add the user, check if this arc's already full.
			// if so, push the last user out before continuing.
			if (this.FillsRing)  // ( so we may fill the ring --> may need to bleed out)
			{
				UIArc nextArcInLoc = parentLoc.NextArcInLoc(this);
				UIUser lastUser = this.LastUser;
				this.RemoveUser(lastUser);  // remove the user (but don't modify anything else graphically)
				if (nextArcInLoc == null)  // if there are no more arcs for this loc
				{
					// if we filled up the outermost arc, create a new one on the next ring
					nextArcInLoc = parentLoc.AddNewArc(); // returns a new arc
				}
				if (lastUser != null)   // lastuser could be null if the last user just
				{                       // got pushed out of this arc.
					// add the user to the front of the next arc
					nextArcInLoc.AddUserToFront(lastUser.Username);
				}
			}
			
			UIUser newUser = new UIUser(username);      // create a new user
            newUser.UserMouseEnter += new UIUser.UserUIEvent(newUser_UserMouseEnter);

            newUser.tickIndex = 0;
            Users.AddFirst(newUser);
            AddUserToCanvas(newUser);   // add to the canvas, and apply transforms

            // push any existing users forward (starting at the 2nd one)
            LinkedListNode<UIUser> user = Users.First.Next;
            while (user != null)
            {
                // move the user forward one unit
                MoveUser(user.Value, user.Value.tickIndex + 1);
                // if needed, expand the arc
                if (user.Value.tickIndex == this.endTick) this.Expand();
                user = user.Next;
            }


        }

        public void AddUser(string username)
        {
            UIUser newUser = new UIUser(username);      // create a new user
            newUser.UserMouseEnter += new UIUser.UserUIEvent(newUser_UserMouseEnter);

            // give the new user the tick index of the end.
            newUser.tickIndex = 0;
            if (Users.Count > 0) newUser.tickIndex = Users.Last.Value.tickIndex + 1;
            // at the end of the list
            Users.AddLast(new LinkedListNode<UIUser>(newUser));
            AddUserToCanvas(newUser);   // add to the drawing canvas, and apply transforms to position
        }

        void newUser_UserMouseEnter(UIUser user, System.Windows.Input.MouseEventArgs mouseArgs)
        {
            //HighlightUser(user.Username);
			// jump back to the parent viz and raise a mouseover event.
            parentLoc.ParentViz.RaiseMouseOver(user, parentLoc.Loc_ID, mouseArgs);
        }

        public void AddUserToCanvas(UIUser newUser)
        {

            double heightOver2 = newUser.userButton.Height / 2;

            int absTick = newUser.tickIndex; // + this.tickIndex;
            
            // this transform moves the user to the correct location,
            // and can be animated if the user moves location within their arc.
            Canvas.SetLeft(newUser.userButton, innerRadius + thickness / 2 - 50);
            Canvas.SetTop(newUser.userButton, -1* heightOver2);

			double userAng = arcRing.angleOf(absTick) + arcRing.angleOf(1) / 2;
			if (arcRing.CCW) userAng = -1 * userAng;	// reverse angles for CCW arcs.

			TransformCollection TC = new TransformCollection();
			TC.StatusOfNextUse = UseStatus.ChangeableReference;

			newUser.myInPlaceRT = new RotateTransform(-1*userAng, new Point(50, heightOver2));
			newUser.myInPlaceRT.StatusOfNextUse = UseStatus.ChangeableReference;

			TC.Add(userInPlaceRotate);
			TC.Add(newUser.myInPlaceRT);
			
			newUser.myRT = new RotateTransform(userAng, 
                new Point(-1 * innerRadius - thickness / 2 + 50, heightOver2));
            newUser.myRT.StatusOfNextUse = UseStatus.ChangeableReference;
			TC.Add(newUser.myRT);

            //newUser.userCanvas.RenderTransform = newUser.myRT;
			newUser.userButton.RenderTransform = TC; //newUser.myRT;

            //arcCanvas.Children.Add(newUser.userCanvas);
            arcCanvas.Children.Add(newUser.userButton);

            // always fade in the new user
            DoubleAnimation fadeInAnimation = new DoubleAnimation(
                0.0,    // from transparent
                1.0,    // to fully opaque
                new Duration(TimeSpan.FromSeconds(1)), FillBehavior.HoldEnd);
            fadeInAnimation.BeginTime = TimeManager.CurrentGlobalTime;
            newUser.userButton.GetAnimations(Button.OpacityProperty).Add(fadeInAnimation);
 
            if((this.EndTick - this.StartTick) == newUser.tickIndex) Expand();  // expand the current arc if we need room

        }



        public void MoveForward()
        {
            // move the next arc out of the way (this may start a recursive move of all arcs in from of this one)
            UIArc nextArc = parentLoc.NextArc(this);  // find the arc after this one (regardless of where it is.)
			UIArc nextArcInLoc = parentLoc.NextArcInLoc(this);
            if (nextArc != null)
            {   // move the arc out of the way (but only if it's actually in the way)
                if (nextArc.RingIndex == this.RingIndex)
                {   // if the next arc is on the same ring, check if it's in the way
                    if (nextArc.StartTick == this.endTick)
                        nextArc.MoveForward();
                }
                else  //the next arc is on a different ring
                {
                    // next arc is on the very next ring, and is located at the start of that ring  AND this arc isn't in our loc (else it will be bled into)
                    if ((nextArc.RingIndex == this.ringIndex + 1) && (nextArc.startTick == 0) && (nextArc != nextArcInLoc))
                        nextArc.MoveForward();  // so, move it forward one.
                }
            }

            if (this.FillsRing)  // ( so we may fill the ring --> may need to bleed out)
            {
                UIUser lastUser = this.LastUser;
                this.RemoveUser(lastUser);  // remove the user (but don't modify anything graphically)
            
                if (nextArcInLoc == null)  // if there are no more arcs for this loc
                {
                    // if we filled up the outermost arc, create a new one on the next ring
                    nextArcInLoc = parentLoc.AddNewArc(); // returns a new arc
                }
                if (lastUser != null)   // lastuser could be null if the last user just
                {                       // got pushed out of this arc.
                    // add the user to the front of the next arc
                    nextArcInLoc.AddUserToFront(lastUser.Username);
                }
                changeArc(startTick + 1, endTick);         // shrink the arc, keeping the end the same.
                // note: if the arc shrinks to nothing, changeArc removes it from the parent loc and it is
                //       scheduled for removal and destruction once the animation finishes.
            }
            else changeArc(startTick + 1, endTick + 1);    // open space after this arc ... just move this arc.
        }

		public void SetBrightness(double value)
		{
			arcPath.Opacity = value;
		}

        private void Expand()
        {
            // move the next arc out of the way (this may start a recursive move of all arcs in from of this one)
            UIArc nextArc = parentLoc.NextArc(this);  // find the arc after this one (regardless of where it is.)
            if (nextArc != null)
            {   // move the arc out of the way (but only if it's actually in the way)
                if (nextArc.StartTick == this.endTick) nextArc.MoveForward();
                // ToDo: worry about things like bleeding to the next arc
            }

            changeArc(startTick, endTick + 1);         // when the call returns, finally expand this arc.
        }

        private UIUser FindUser(string username)
        {
            // Search the linked list for the user.
            LinkedListNode<UIUser> user = Users.First;
            while (user != null)
            {
                if (user.Value.Username == username) return user.Value;
                user = user.Next;
            }

            return null;   // if we didn't find the user, return null
        }

        /// <summary>
        /// This function removes a user without any reprecussions to other users or
        /// the parent arc.  This is used in the other version of RemoveUser, and other places.
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
           public void RemoveUser(UIUser user)
        {
            try
            {
                // 1. remove the user from the canvas
                arcCanvas.Children.Remove(user.userButton);
                // 2. remove the user from the linked list
                Users.Remove(user);
            }
            catch
            {
                System.Diagnostics.Debug.WriteLine("Unknown error removing user: " + user.Username);
            }
        }


        public bool RemoveUser(string username)
        {
            bool FoundUser = false;

            // Search the linked list for the user.
            LinkedListNode<UIUser> user = Users.First;
            while (user != null)
            {
                if (user.Value.Username == username)
                {
                    // we located the target user!
                    FoundUser = true;
                    // get rid of the user. but first ... 
                    LinkedListNode<UIUser> nextNode = user.Next;  // keep a pointer to the next item.
                    RemoveUser(user.Value);
                    user = nextNode;    // restore the pointer to the next item
                    break;              // break out of the while
                }
                user = user.Next;
            }

            if(!FoundUser) return false;    // quit now if we didn't find the user
            
            // now, we've either reached the end (user=null) or we've removed the user
            // so, pull the other users back one unit so we don't leave a gap.
            while (user != null)
            {
                // move the user back one unit
                MoveUser(user.Value, user.Value.tickIndex - 1);
                user = user.Next;
            }

            if (FillsRing)
            {
                UIArc nextArcInLoc = parentLoc.NextArcInLoc(this);
                // get the next arc in this loc (if one exists)
                if (nextArcInLoc != null)
                {
                    UIUser firstUser = nextArcInLoc.FirstUser;
                    if (firstUser != null)
                    {
                        nextArcInLoc.RemoveUser(firstUser.Username);
                        this.AddUser(firstUser.Username);   // arc won't be modified because
                                                            // there's room for the user.
                        return true;    // return before the arc gets modified.
                    }
                }
            }
            
            // if we're here, we didn't pull any users from a future arc. so, just shrink the arc.
            changeArc(startTick, endTick - 1);
            return true;

        }

        void MoveUser(UIUser User, int toIndex)
        {
            // animates a user from their current tick to a target tick, moving them by rotating them.

			double userAngFrom = arcRing.angleOf(User.tickIndex) + arcRing.angleOf(1) / 2;
			double userAngTo = arcRing.angleOf(toIndex) + arcRing.angleOf(1) / 2;
			if (arcRing.CCW)  // reverse angles for CCW arcs.
			{ userAngFrom = -1 * userAngFrom;	userAngTo = -1 * userAngTo;	}
			

            DoubleAnimation myDoubleAnim = new DoubleAnimation(userAngFrom, userAngTo, 
				new Duration(TimeSpan.FromSeconds(1)),FillBehavior.HoldEnd);
			myDoubleAnim.BeginTime = TimeManager.CurrentGlobalTime;

			DoubleAnimation myDoubleAnim2 = new DoubleAnimation(-1*userAngFrom,
							-1*userAngTo, new Duration(TimeSpan.FromSeconds(1)), FillBehavior.HoldEnd);
			myDoubleAnim2.BeginTime = TimeManager.CurrentGlobalTime;

            User.tickIndex = toIndex;   // change the actual index permanently
            User.myRT.GetAnimations(RotateTransform.AngleProperty).Add(myDoubleAnim);
			User.myInPlaceRT.GetAnimations(RotateTransform.AngleProperty).Add(myDoubleAnim2);

        }

        /// <summary>
        /// Changes an arc by animating it to the target values.
        /// </summary>
        /// <param name="mainArc">a reference to the arc</param>
        /// <param name="newStartTick">The new starting value within the ring.</param>
        /// <param name="newEndTick">The new ending value within the ring.</param>
        public void changeArc(int newStartTick, int newEndTick)
        {
       
            double endX, endY;

            double newStartAngle = arcRing.angleOf(newStartTick) * (Math.PI / 180);
            double newEndAngle = arcRing.angleOf(newEndTick) * (Math.PI / 180);

            double oldEndAngle = endAngle * (Math.PI / 180);
            double oldStartAngle = startAngle * (Math.PI / 180);

            double newAngularDistance = newEndAngle - newStartAngle;
            double oldAngularDistance = oldEndAngle - oldStartAngle;

            Double deltaAngle = newAngularDistance - oldAngularDistance;

            Double TotalDuration = 1;   // 2 seconds

            // Create a timeline that will generate this animation effect.

            ParallelTimeline myParallelTimeline = new ParallelTimeline();
            myParallelTimeline.FillBehavior = FillBehavior.HoldEnd;
            myParallelTimeline.BeginTime = NullableTimeSpan.Null;

            //int stepCount = 1; // number of steps in the approximation
            int stepCount = parentLoc.ParentViz.RotateStepCount;

            Double stepDuration = TotalDuration / stepCount;

            PointAnimation[] OuterEndpointAnim = new PointAnimation[stepCount];
            PointAnimation[] InnerEndpointAnim = new PointAnimation[stepCount];

            // This animates the "Large Arc" flags of both the inner and outer arc segs.
            KeyFrameBooleanAnimation LargeArcFlagAnim = new KeyFrameBooleanAnimation();
            LargeArcFlagAnim.Duration = TimeSpan.FromSeconds(TotalDuration);
            LargeArcFlagAnim.BeginTime = TimeSpan.FromSeconds(0);
            LargeArcFlagAnim.FillBehavior = FillBehavior.HoldEnd;
            LargeArcFlagAnim.AllowChangeableReferenceOverride = true; // IMPORTANT!
            
            DiscreteBooleanKeyFrame keyFrame = new DiscreteBooleanKeyFrame(
              (oldAngularDistance > Math.PI),   // force it to the correct initial state
              KeyTime.FromPercent(0));
            LargeArcFlagAnim.KeyFrames.Add(keyFrame);
                        
            // fraction of total time when we cross the 180 degree margin
            double frac = (Math.PI - oldAngularDistance) / deltaAngle;
            // if the state of LargeArc changes during the animation, add a keyframe to flip it
            if (frac >= 0.0 && frac <= 1.0)
            {
                keyFrame = new DiscreteBooleanKeyFrame(
                    (deltaAngle > 0),   // goes from false --> true if we're increasing angle
                    KeyTime.FromPercent(frac));
                LargeArcFlagAnim.KeyFrames.Add(keyFrame);
            }


            for (int i = 0; i < stepCount; i++)
            {
                // compute incremental endpoints along the arc (on the unit circle)
                endX = Math.Cos(oldAngularDistance + (deltaAngle / stepCount) * (i + 1));
                endY = Math.Sin(oldAngularDistance + (deltaAngle / stepCount) * (i + 1));
				if(arcRing.CCW) endY = -1 * endY;	// draw in a CCW direction if the ring is CCW

                OuterEndpointAnim[i] = new PointAnimation();
                OuterEndpointAnim[i].To = new System.Windows.Point(endX * outerRadius, endY * outerRadius);
                OuterEndpointAnim[i].BeginTime = TimeSpan.FromSeconds(i * stepDuration);
                OuterEndpointAnim[i].Duration = TimeSpan.FromSeconds(stepDuration);
                OuterEndpointAnim[i].FillBehavior = FillBehavior.HoldEnd;

                InnerEndpointAnim[i] = new PointAnimation();
                InnerEndpointAnim[i].To = new System.Windows.Point(innerRadius * endX, innerRadius * endY);
                InnerEndpointAnim[i].BeginTime = TimeSpan.FromSeconds(i * stepDuration);
                InnerEndpointAnim[i].Duration = TimeSpan.FromSeconds(stepDuration);
                InnerEndpointAnim[i].FillBehavior = FillBehavior.HoldEnd;

                myParallelTimeline.Children.Add(OuterEndpointAnim[i]);
                myParallelTimeline.Children.Add(InnerEndpointAnim[i]);

            }
          

            // Now get the rotate transform from the arc and animate it!

			// reverse the angles for CCW rings.
			if (arcRing.CCW) { oldStartAngle = -1 * oldStartAngle; newStartAngle = -1 * newStartAngle; }
           
            // Create an animation and use it to animate the angle of the rotation
            DoubleAnimation myDoubleAnimation =
                new DoubleAnimation(
                oldStartAngle * (180 / Math.PI),        // Start
                newStartAngle * (180 / Math.PI),        // End
                new Duration(TimeSpan.FromSeconds(TotalDuration))
                );

            myDoubleAnimation.FillBehavior = FillBehavior.HoldEnd;
            myDoubleAnimation.BeginTime = TimeSpan.FromSeconds(0);
			myDoubleAnimation.StatusOfNextUse = UseStatus.ChangeableReference;

			// Create an animation and use it to animate the angle of the rotation
			DoubleAnimation myUserDoubleAnimation =
				new DoubleAnimation(
				-1*oldStartAngle * (180 / Math.PI),        // Start
				-1*newStartAngle * (180 / Math.PI),        // End
				new Duration(TimeSpan.FromSeconds(TotalDuration))
				);

			myUserDoubleAnimation.FillBehavior = FillBehavior.HoldEnd;
			myUserDoubleAnimation.BeginTime = TimeSpan.FromSeconds(0);
			myUserDoubleAnimation.StatusOfNextUse = UseStatus.ChangeableReference;
			
			myParallelTimeline.Children.Add(LargeArcFlagAnim);  // 3rd to last one --> large arc anim.      
            myParallelTimeline.Children.Add(myDoubleAnimation); //  2nd to last one --> rotation animation
			myParallelTimeline.Children.Add(myUserDoubleAnimation); // VERY last one --> rotation animation for users

            TimelineClock myClock = myParallelTimeline.CreateClock();   // create a root clock w/children

            for (int i = 1; i < stepCount; i++)
            {

				// Animate the Point property!
                ((ArcSegment)((PathFigure)((PathGeometry)arcPath.Data).Figures[0]).Segments[1])
                    .GetAnimationClocks(ArcSegment.PointProperty).Add((AnimationClock)myClock.Children[i * 2]);

                // Animate the 2nd Point property!
                ((LineSegment)((PathFigure)((PathGeometry)arcPath.Data).Figures[0]).Segments[2])
                    .GetAnimationClocks(LineSegment.PointProperty).Add((AnimationClock)myClock.Children[(i * 2) + 1]);
            }

            // Animate the LargeArc property! (index of this clock... the very last one)
            ((ArcSegment)((PathFigure)((PathGeometry)arcPath.Data).Figures[0]).Segments[1])
                .GetAnimationClocks(ArcSegment.LargeArcProperty).Add((AnimationClock)myClock.Children[stepCount * 2]);

            // Animate the LargeArc property! (index of this clock... 3rd to last one)
            ((ArcSegment)((PathFigure)((PathGeometry)arcPath.Data).Figures[0]).Segments[3])
                .GetAnimationClocks(ArcSegment.LargeArcProperty).Add((AnimationClock)myClock.Children[stepCount * 2]);

            // Add clock to the rotate transform (the 2nd to LAST clock)
            Rotate.GetAnimationClocks(RotateTransform.AngleProperty).Add((AnimationClock)myClock.Children[stepCount * 2 + 1]);

			// Add clock to the rotate transform (the LAST clock)
			userInPlaceRotate.GetAnimationClocks(RotateTransform.AngleProperty).Add((AnimationClock)myClock.Children[stepCount * 2 + 2]);


            // Apply the settings to the angle values
            startTick = newStartTick; endTick = newEndTick;
            if (startTick == endTick)
            {
                parentLoc.RemoveArc(this);    // remove this arc since it's now empty.
                                              // the arc will stay alive while it's being animated away.
                removeWhenDone = true;        // and when the animation is finished, it will request
            }                                 // to be permanently removed from the canvas.
            
            myClock.ClockController.Begin();
            myClock.Ended += new EventHandler(myClock_Ended);


        }

        void myClock_Ended(object sender, EventArgs e)
        {
            clearAnimations();
          
            // when the animation's done, and our size is set to 0, remove this arc entirely from the viz
            if (removeWhenDone) { parentLoc.RemoveArcFromCanvas(this); removeWhenDone = false; return; }

            // otherwise, continue and force the final values to their targets.

            double angularSize = (endAngle - startAngle) * (Math.PI / 180);

            // computed points on the unit circle (INNER RADIUS)
			double rads = 0; // (4.0 / innerRadius); // this creates a uniform-width gap between locations
            double endXi = Math.Cos(angularSize - rads);
			double endYi = Math.Sin(angularSize - rads); if (arcRing.CCW) endYi = -1 * endYi;

            // computed points on the unit circle (OUTER RADIUS)
			rads = 0; // (4.0 / outerRadius); // this creates a uniform-width gap between locations
            double endXo = Math.Cos(angularSize - rads);
			double endYo = Math.Sin(angularSize - rads); if (arcRing.CCW) endYo = -1 * endYo;


            // Set the point property.
            ((LineSegment)((PathFigure)((PathGeometry)arcPath.Data).Figures[0]).Segments[2])
                .Point = new Point(innerRadius * endXi, innerRadius * endYi);

            // Set the outer point
            ((ArcSegment)((PathFigure)((PathGeometry)arcPath.Data).Figures[0]).Segments[1])
                .Point = new System.Windows.Point(endXo * outerRadius, endYo * outerRadius);

            // Set the largeArc property
            ((ArcSegment)((PathFigure)((PathGeometry)arcPath.Data).Figures[0]).Segments[1])
               .LargeArc = (angularSize > Math.PI);

            // Set the largeArc
            ((ArcSegment)((PathFigure)((PathGeometry)arcPath.Data).Figures[0]).Segments[3])
               .LargeArc = (angularSize > Math.PI);

            // This method removes the animations associated with different pieces of the
            // display so that there are no memory problems when the app finishes.
            return;
        }

        void clearAnimations()
        {
            // This method removes the animations associated with different pieces of the
            // display so that there are no memory problems when the app finishes.

            ((ArcSegment)((PathFigure)((PathGeometry)arcPath.Data).Figures[0]).Segments[1])
                .GetAnimationClocks(ArcSegment.PointProperty).Clear();
            ((LineSegment)((PathFigure)((PathGeometry)arcPath.Data).Figures[0]).Segments[2])
                .GetAnimationClocks(LineSegment.PointProperty).Clear();
            ((ArcSegment)((PathFigure)((PathGeometry)arcPath.Data).Figures[0]).Segments[1])
                .GetAnimationClocks(ArcSegment.LargeArcProperty).Clear();
            ((ArcSegment)((PathFigure)((PathGeometry)arcPath.Data).Figures[0]).Segments[3])
                .GetAnimationClocks(ArcSegment.LargeArcProperty).Clear();
        }

		private Color brighten(Color colorIn)
		{
			byte r = (byte)Math.Min(255, (int)colorIn.R + 50);	// increase the color but don't let it go over 255
			byte g = (byte)Math.Min(255, (int)colorIn.G + 50);
			byte b = (byte)Math.Min(255, (int)colorIn.B + 50);
			return Color.FromRgb(r, g, b);
		}

        public UIArc(UILocation Parent, int StartTick, int RingIndex)
        {
			
            this.ringIndex = RingIndex; this.arcRing = new Ring(ringIndex);
            this.startTick = StartTick;
            this.endTick = StartTick;

            parentLoc = Parent;
            arcCanvas = new Canvas();
            
            arcCanvas.Width = 300;
            arcCanvas.Height = 300;

            arcCanvas.Margin = new Thickness(20);
            
            double angularSize = endAngle - startAngle;

            // computed points on the unit circle
            double endX = Math.Cos(angularSize);
			double endY = Math.Sin(angularSize); if (arcRing.CCW) endY = -1 * endY;

            bool bigAngle = Math.Abs(startAngle) > Math.PI;

            // Create a Path element to render the shapes.
            arcPath = new System.Windows.Shapes.Path();
			arcPath.Fill = parentLoc.ColorBrush;
			strokeBrush = new SolidColorBrush(brighten(parentLoc.Color));
			strokeBrush.StatusOfNextUse = UseStatus.ChangeableReference;
			arcPath.Stroke = strokeBrush;

			Bind myNewBindDef = new Bind("Highlighted");

			// Envelope all property changes within a defer block.
			using (myNewBindDef.DeferChanges())
			{
				myNewBindDef.BindType = BindType.OneWay;
				myNewBindDef.Source = myChangedData;
				myNewBindDef.Converter = TheConverter;
				myNewBindDef.Culture = new System.Globalization.CultureInfo("en-US");
			}
			// Bind the stroke to the highlighted boolean value.
			//arcPath.SetBinding(Path.StrokeProperty, myNewBindDef);
			
			arcPath.StrokeThickness = 2;

            // Create a PathGeometry to contain the PathFigure.
            System.Windows.Media.PathGeometry myPathGeometry =
            new System.Windows.Media.PathGeometry();

            // Create a PathFigure to describe the shape.
            System.Windows.Media.PathFigure myPathFigure =
            new System.Windows.Media.PathFigure();

            //myPathFigure.StatusOfNextUse = UseStatus.ChangeableReference;

            // start at x, 0, where x is the outer radius
            myPathFigure.StartAt(new System.Windows.Point(outerRadius, 0));
            // Draw the arc up from that point

            myPathFigure.StatusOfNextUse = UseStatus.ChangeableReference;

            myPathFigure.ArcTo(
               new System.Windows.Point(endX * outerRadius, endY * outerRadius),
               new System.Windows.Size(outerRadius, outerRadius), 0, bigAngle, !arcRing.CCW);

            myPathFigure.LineTo(new Point(innerRadius * endX, innerRadius * endY));
            myPathFigure.ArcTo(
              new System.Windows.Point(innerRadius, 0),
			  new System.Windows.Size(innerRadius, innerRadius), 0, bigAngle, arcRing.CCW);
            myPathFigure.Close(); // Straight line to finish up the object

            myPathGeometry.AddFigure(myPathFigure);
            arcPath.Data = myPathGeometry;

            //Child = arcCanvas;

            //TDArc = new TransformDecorator();
            Rotate = new RotateTransform(startAngle * (180 / Math.PI), new Point(0, 0));
            Rotate.StatusOfNextUse = UseStatus.ChangeableReference;

			userInPlaceRotate = new RotateTransform((-1 * startAngle * (180 / Math.PI)), new Point(50, 15));
			userInPlaceRotate.StatusOfNextUse = UseStatus.ChangeableReference;

            arcCanvas.RenderTransform = Rotate;
            arcCanvas.Children.Add(arcPath);

            //TDArc.Transform = Rotate;
            //TDArc.AffectsLayout = false;
            //TDArc.Child = arcPath;

        }
        public class Ring
        {

			public static double arcGap = 3.0;

            public double tickSize
            {
                get { return 360 / (double)maxTick; }
            }

			// determines whether arcs in this ring are drawn in the CW or CCW direction
			public bool CCW { get { return ccw; } }

            public int maxTick;
			private bool ccw;			// the arc is to be drawn counterclockwise!
            public double innerRadius;
            public double thickness
            {
                get { return 50 - arcGap; }	// 1 pixel of overlap causes locations to fuse.
            }
            public double angleOf(int tick)
            {
                // return a fraction of the maxTick ( IN DEGREES )
                
                double ang = ((double)tick / (double)maxTick) * 360;
                if (ang == 360) ang = 359.999;
                //if (ccw && ang == 0) ang = 0.001;

                return ang;
            }
            public Ring(int i)
            {
				ccw = (i % 2) == 1;	// the 0th ring is CCW, 1st is CW, 2=CCW, 3=CW...
                innerRadius = 50 * i;
                maxTick = 8 * i;
            }
        }

    }


}
